/* *************************************************
 * Programmer: Narb M (webmaster@narbware.com)
 * Date: 11/30/04
 * Company Info: www.Nwarbware.com
 * *************************************************
 * 
 * TERMS OF USE:
 * -------------
 * You may freely include and compile and modify this class in your applications with 
 * the following limitations:
 * 
 * - The TERMS OF USE section of this file must remain unmodified in the source.
 * - This source or modified versions of this source cannot be sold for profit or claimed your own.
 * - Use of this code or any part for distrabution must include the TERMS OF USE in the source.
 * - Narbware cannot be held responsible for any damage resulting from this source.
 * - Any questions about the source or terms of use can be directed to: webmaster@narbware.com.
 * 
 * THE PROBLEM
 * -----------
 * When trying to use 32bit alphablended images or icons for certain controls in the .NET framework
 * using the imagelist control, the alphachannel info becomes lost in the process of ADDING the images 
 * to the imagelist control. The bug seems to be in the .Add() method of the imagelist control since the
 * imagelist control can hold alphablended images with no problems. This results in images displayed on
 * the form to be shown with a black border and in some cases incorrect alpha pixel color depending
 * on the file format.
 * 
 * THE FIX
 * -------
 * To overcome this problem with the .Add() method, This class attempts to use other means of adding
 * alphablended images to an imagelist control, therefore allowing use of 32bit alphablended images.
 * 
 * USAGE
 * -----
 * To use these methods. You can make a direct call to the static methods from your code without the
 * need to create a new instance of the class. an example of this would look like the following:
 * ...
 * Narbware.Imagelist.AddFromFile( "C:\\image.png", myImagelist ); // Adds an alphablended image to the imagelist
 * Narbware.Imagelist.AddEmbeddedResource( "MyApp.image.ico", myImagelist ); // Adds an alphablended image from embedded resource
 * Narbware.Imagelist.AddFromImage( myImage, myImagelist ); // Adds the Image object to the imagelist
 * Narbware.Imagelist.AddIconOfFile( "C:\\myFile.exe", Narbware.IconSize.Large, true, false, false, myImagelist );
 * Narbware.Imagelist.AddIconOfFileExt( ".zip", Narbware.IconSize.Small, false, false, false, myImagelist );
 * ...
 * The above static calls would add alphablended images to the imagelist. If the image is not
 * a valid 32bit alphapixel foramt image then it is added by the default imagelist.Add() method.
 * 
 * ***** REQUIREMENTS *****
 * ------------------------
 * - The imagelist controls ColorDepth property MUST be set to Depth32Bit.
 * - A call to Application.EnableVisualStyles() AND Application.DoEvents() MUST be made in the Main()
 *   and placed BEFORE the Application.Run() method.
 * - Images and icons must be of correct format (32bit) and contain alpha pixels. else the images will be displayed normally.
 * - Windows XP+ (not sure of windows 2000)
 * - .NET Framework V1.1+
 */

using System;
using System.Reflection;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Drawing.Imaging;

namespace Narbware
{
	public enum IconSize{ Large, Small, Shell };

	/// <summary>
	/// This class contains static methods to overcome the problems with the imagelist control
	/// and 32bit alphablended images. The methods allow you to add alphablended images to an
	/// existing imagelist control from an external image or icon file, an embedded resource,
	/// extracted file icon and extracted file extension icon.
	/// </summary>
	public sealed class Imagelist
	{
		/// <summary>
		/// API Constants
		/// </summary>
		private const uint SHGFI_ICON				= 0x100;	// get icon
		private const uint SHGFI_LINKOVERLAY		= 0x8000;	// put a link overlay on icon
		private const uint SHGFI_SELECTED			= 0x10000;	// show icon in selected state
		private const uint SHGFI_LARGEICON			= 0x0;		// get large icon
		private const uint SHGFI_SMALLICON			= 0x1;		// get small icon
		private const uint SHGFI_OPENICON			= 0x2;		// get open icon
		private const uint SHGFI_SHELLICONSIZE		= 0x4;		// get shell size icon
		private const uint SHGFI_USEFILEATTRIBUTES	= 0x10;		// use passed dwFileAttribute
		private const uint SHGFI_TYPENAME			= 0x400;	// get file type name

		/// <summary>
		/// A SHFILEINFO structure used to extract the icon resource of a file or folder.
		/// </summary>
		[StructLayout(LayoutKind.Sequential)]
		private struct SHFILEINFO 
		{
			public IntPtr hIcon;
			public IntPtr iIcon;
			public uint dwAttributes;
			[MarshalAs( UnmanagedType.ByValTStr, SizeConst = 260 )]
			public string szDisplayName;
			[MarshalAs( UnmanagedType.ByValTStr, SizeConst = 80 )]
			public string szTypeName;
		};

		/// <summary>
		/// A BITMAPINFO stucture used to store an alphablended bitmap for adding to imagelist.
		/// </summary>
		[StructLayout( LayoutKind.Sequential )]
		private class BITMAPINFO
		{
			public Int32 biSize;
			public Int32 biWidth;
			public Int32 biHeight;
			public Int16 biPlanes;
			public Int16 biBitCount;
			public Int32 biCompression;
			public Int32 biSizeImage;
			public Int32 biXPelsPerMeter;
			public Int32 biYPelsPerMeter;
			public Int32 biClrUsed;
			public Int32 biClrImportant;
			public Int32 colors;
		};

		[DllImport("comctl32.dll")]
		private static extern bool ImageList_Add( IntPtr hImageList, IntPtr hBitmap, IntPtr hMask );
		[DllImport("kernel32.dll")]
		private static extern bool RtlMoveMemory( IntPtr dest, IntPtr source, int dwcount );
		[DllImport("shell32.dll")]
		private static extern IntPtr SHGetFileInfo( string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags );
		[DllImport("user32.dll")]
		private static extern IntPtr DestroyIcon( IntPtr hIcon );
		[DllImport("gdi32.dll")]
		private static extern IntPtr CreateDIBSection( IntPtr hdc, [In, MarshalAs(UnmanagedType.LPStruct)]BITMAPINFO pbmi, uint iUsage, out IntPtr ppvBits, IntPtr hSection, uint dwOffset );

		private Imagelist(){}

		/// <summary>
		/// Adds an alphablended image to an existing imagelist from an Image object.
		/// </summary>
		/// <param name="sourceImage">The Image object to add to the imagelist.</param>
		/// <param name="destinationImagelist">An existing imagelist to add the image to.</param>
		/// <example>.AddFromImage( myImage, toolbarImageList );</example>
		public static void AddFromImage( Image sourceImage, ImageList destinationImagelist )
		{
			Add( (Bitmap)sourceImage, destinationImagelist );
		}

		/// <summary>
		/// Adds an alphablended icon to an existing imagelist from an Icon object.
		/// </summary>
		/// <param name="sourceIcon">The Icon object to add to the imagelist</param>
		/// <param name="destinationImagelist">An existing imagelist to add the icon to.</param>
		/// <example>.AddFromImage( myIcon, toolbarImageList );</example>
		public static void AddFromImage( Icon sourceIcon, ImageList destinationImagelist )
		{
			destinationImagelist.Images.Add( Icon.FromHandle( sourceIcon.Handle ) );
		}

		/// <summary>
		/// Adds an alphablended icon or image to an existing imagelist from an external image or icon file.
		/// </summary>
		/// <param name="FileName">The full path to a 32bit alphablended file.</param>
		/// <param name="Imagelist">An existing imagelist to add the image or icon to.</param>
		/// <example>.FromFile( "C:\\file.ico", toolbarImagelist );</example>
		public static void AddFromFile( string fileName, ImageList destinationImagelist )
		{
			Bitmap bm = new Bitmap(fileName);

			if ( bm.RawFormat.Guid == ImageFormat.Icon.Guid )
			{
				Icon icn = new Icon( fileName );
				destinationImagelist.Images.Add( Icon.FromHandle( icn.Handle ) );
				icn.Dispose();
			}
			else
			{
				Add( bm, destinationImagelist );
			}
			bm.Dispose();
		}

		/// <summary>
		/// Extracts the icon of an existing file or folder on the system and adds the icon to an imagelist.
		/// </summary>
		/// <param name="fileName">A file or folder path to extract the icon from.</param>
		/// <param name="iconSize">The size to extract.</param>
		/// <param name="selectedState">A flag to add the selected state of the icon.</param>
		/// <param name="openState">A flag to add the open state of the icon.</param>
		/// <param name="linkOverlay">A flag to add a shortcut overlay to the icon.</param>
		/// <param name="destinationImagelist">An existing imagelist to add the extracted icon to.</param>
		/// <example>.AddIconOfFile( "C:\\folder\file.exe", Narbware.IconSize.Small, false, false, false, toolbarImageList );</example>
		public static void AddIconOfFile( string fileName, IconSize iconSize, bool selectedState, bool openState, bool linkOverlay, ImageList destinationImagelist )
		{
			uint uFlags =	((iconSize == IconSize.Large)? SHGFI_LARGEICON: 0) |
							((iconSize == IconSize.Small)? SHGFI_SMALLICON: 0) |
							((iconSize == IconSize.Shell)? SHGFI_SHELLICONSIZE: 0) |
							((selectedState)? SHGFI_SELECTED: 0) |
							((openState)? SHGFI_OPENICON: 0) |
							((linkOverlay)? SHGFI_LINKOVERLAY: 0);

			Add( fileName, destinationImagelist, uFlags );
		}
		
		/// <summary>
		/// Extracts the icon of an existing file or folder on the system using default parameters and adds the icon to an imagelist.
		/// </summary>
		/// <param name="fileName">A file or folder path to extract the icon from.</param>
		/// <param name="destinationImagelist">An existing imagelist to add the extracted icon to.</param>
		/// <example>.AddIconOfFile( "C:\\folder\file.exe", toolbarImageList );</example>
		public static void AddIconOfFile( string fileName, ImageList destinationImagelist )
		{
			Add( fileName, destinationImagelist, 0 );
		}

		/// <summary>
		/// Extracts a file extension icon from the system and adds the icon to an imagelist.
		/// </summary>
		/// <param name="fileExtension">A file extension to extract the icon from.</param>
		/// <param name="iconSize">The size to extract.</param>
		/// <param name="selectedState">A flag to add the selected state of the icon.</param>
		/// <param name="openState">A flag to add the open state of the icon.</param>
		/// <param name="linkOverlay">A flag to add a shortcut overlay to the icon.</param>
		/// <param name="destinationImagelist">An existing imagelist to add the extracted icon to.</param>
		/// <example>.AddIconOfFileExt( ".jpg", Narbware.IconSize.Large, false, false, false, toolbarImagelist );</example>
		public static void AddIconOfFileExt( string fileExtension, IconSize iconSize, bool selectedState, bool openState, bool linkOverlay , ImageList destinationImagelist )
		{
			uint uFlags =	SHGFI_USEFILEATTRIBUTES |
							((iconSize == IconSize.Large)? SHGFI_LARGEICON: 0) |
							((iconSize == IconSize.Small)? SHGFI_SMALLICON: 0) |
							((iconSize == IconSize.Shell)? SHGFI_SHELLICONSIZE: 0) |
							((selectedState)? SHGFI_SELECTED: 0) |
							((openState)? SHGFI_OPENICON: 0) |
							((linkOverlay)? SHGFI_LINKOVERLAY: 0);

			Add( fileExtension.Insert( 0, "." ), destinationImagelist, uFlags );
		}
		
		/// <summary>
		/// Extracts a file extension icon from the system using default parameters and adds the icon to an imagelist.
		/// </summary>
		/// <param name="fileExtension">A file extension to extract the icon from.</param>
		/// <param name="destinationImagelist">An existing imagelist to add the extracted icon to.</param>
		/// <example>.AddIconOfFileExt( ".jpg", toolbarImagelist );</example>
		public static void AddIconOfFileExt( string fileExtension, ImageList destinationImagelist )
		{
			Add( fileExtension.Insert( 0, "." ), destinationImagelist, SHGFI_USEFILEATTRIBUTES );
		}

		/// <summary>
		/// Adds a 32bit alphablended icon or image to an existing imagelist from an embedded image or icon.
		/// </summary>
		/// <param name="ResourceName">The name of an embedded resource.</param>
		/// <param name="Imagelist">An existing imagelist to add the resource to.</param>
		/// <example>.AddEmbeddedResource( "ProjectName.file.ico", toolbarImagelist );</example>
		public static void AddEmbeddedResource( string resourceName, ImageList destinationImagelist)
		{
			Bitmap bm = new Bitmap( Assembly.GetExecutingAssembly().GetManifestResourceStream( resourceName ) );

				if ( bm.RawFormat.Guid == ImageFormat.Icon.Guid )
				{
					Icon icn = new Icon( Assembly.GetExecutingAssembly().GetManifestResourceStream( resourceName ) );
					destinationImagelist.Images.Add( Icon.FromHandle( icn.Handle ) );
					icn.Dispose();
				}
				else
				{
					AddFromImage( bm, destinationImagelist );
				}
			bm.Dispose();
		}

		/// <summary>
		/// This method is used to add alphablended system icons to an imagelist using win32 api. This an added feature
		/// which is not included in the framework.
		/// </summary>
		/// <param name="pszPath">Path of the file to extract icon from.</param>
		/// <param name="il">Imagelist to which the image will be added to.</param>
		/// <param name="uFlags">Options that return the specified icon.</param>
		private static void Add( string pszPath, ImageList il, uint uFlags )
		{
			SHFILEINFO SHInfo = new SHFILEINFO();

			IntPtr hImg = SHGetFileInfo( pszPath, 0, ref SHInfo, (uint)Marshal.SizeOf(SHInfo), SHGFI_ICON | uFlags );
			il.Images.Add( Icon.FromHandle( SHInfo.hIcon ) );
			DestroyIcon( SHInfo.hIcon );
		}

		/// <summary>
		/// This method is used to add alphablended images to an imagelist using win32 api functions to overcome the broken
		/// .Add() method of the imagelist control which loses the alphablend info of images.
		/// </summary>
		/// <param name="bm">Bitmap containing alphablended image.</param>
		/// <param name="il">Imagelist to which the image will be added to.</param>
		private static void Add( Bitmap bm, ImageList il )
		{
			IntPtr hBitmap, ppvBits;
			BITMAPINFO bmi = new BITMAPINFO();

			if ( bm.Size != il.ImageSize )
			{	// resize the image to dimensions of imagelist before adding
				bm = new Bitmap( bm, il.ImageSize.Width, il.ImageSize.Height );
			}
			bmi.biSize = 40;			// Needed for RtlMoveMemory()
			bmi.biBitCount = 32;		// Number of bits
			bmi.biPlanes = 1;			// Number of planes
			bmi.biWidth = bm.Width;		// Width of our new bitmap
			bmi.biHeight = bm.Height;	// Height of our new bitmap
			bm.RotateFlip( RotateFlipType.RotateNoneFlipY );	// Required due to the way bitmap is copied
			hBitmap = CreateDIBSection( new IntPtr(0), bmi, 0, out ppvBits, new IntPtr(0), 0 );
			BitmapData bitmapData = bm.LockBits( new Rectangle( 0, 0, bm.Width, bm.Height ), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb );
			RtlMoveMemory( ppvBits, bitmapData.Scan0, bm.Height * bitmapData.Stride ); // copies the bitmap
			bm.UnlockBits( bitmapData );
			ImageList_Add( il.Handle, hBitmap, new IntPtr(0) ); // adds the new bitmap to the imagelist control
		}
	}
}
